﻿using System;


namespace PServiceBus.Serializer.Xml {
    public unsafe sealed class XmlReaderEx {

        private int _index;
        private string _elementName, _xml;
        private bool _isEndElement;
        private string _attributes;
        private int _length;
        const char QuoteStr = '"';
        const char SpaceStr = ' ', GreaterStr = '>', LessStr = '<', BackSlash = '/', Null = '\0';

        public XmlReaderEx(string xml) {
            _length = (_xml = xml).Length;
        }

        public void Reset() {
            _index = 0;
        }

        public string ElementName { get { return _elementName; } }
        public string TypeAttribute { get { return _attributes != null && _attributes.Contains("type") ? this["type"] : null; } }
        public bool HasAttributes { get { return _attributes != null; } }
        public bool IsEndElement { get { return _isEndElement; } }

        public string this[string name] {
            get {
                var index = _attributes.IndexOf(name, StringComparison.Ordinal) + name.Length + 2;
                return _attributes.Substring(index, _attributes.IndexOf(QuoteStr, index, _attributes.Length - index) - index);
            }
        }


        public string GetValue() {
            if (_isEndElement) return string.Empty;
            int count = 1, index = _index;
            fixed (char* p = _xml) {
                if (_length <= index) return string.Empty;
                var current = p[index];
                if (current == LessStr) return string.Empty;
                do {
                    ++index;
                    current = p[index];
                    if (current == LessStr) break;
                    ++count;
                } while (true);
                _index = index;
                return new String(p, index - count, count);
            }
        }

        public bool Read() {
            char current = Null;
            bool hasValue = false, hasAttributes = false, isEndElement = false;
            string elementName = null;
            int count = 0, aindex = 0, index = _index;
            _attributes = null;
            fixed (char* p = _xml) {
                if (_length <= index) return false;
                do {
                    ++count;
                    ++index;
                    current = p[index];
                    if (current == GreaterStr) {
                        ++index;
                        break;
                    }
                    if (null == elementName && current == SpaceStr) {
                        var size = count - 1;
                        elementName = new String(p + index - size, 0, size);
                        count = 0;
                        hasAttributes = true;
                        aindex = index + 1;
                        continue;
                    }
                } while (true);
                if (count > 0) {
                    isEndElement = *(p + index - count) == BackSlash;
                    var diff = (isEndElement ? 1 : 0);
                    var text = new String(p, index - count + diff, count - 1 - diff);
                    if (hasAttributes) {
                        _attributes = text;
                    } else if (elementName == null)
                        elementName = text;
                    hasValue = true;
                }
            }
            _elementName = elementName;
            _isEndElement = isEndElement;
            _index = index;
            return hasValue;
        }

        private static unsafe void memcpy(char* dmem, char* smem, int charCount) {
            if ((((int)dmem) & 2) != 0) {
                dmem[0] = smem[0];
                dmem++;
                smem++;
                charCount--;
            }
            while (charCount >= 8) {
                *((int*)dmem) = *((int*)smem);
                *((int*)(dmem + 2)) = *((int*)(smem + 2));
                *((int*)(dmem + 4)) = *((int*)(smem + 4));
                *((int*)(dmem + 6)) = *((int*)(smem + 6));
                dmem += 8;
                smem += 8;
                charCount -= 8;
            }
            if ((charCount & 4) != 0) {
                *((int*)dmem) = *((int*)smem);
                *((int*)(dmem + 2)) = *((int*)(smem + 2));
                dmem += 4;
                smem += 4;
            }
            if ((charCount & 2) != 0) {
                *((int*)dmem) = *((int*)smem);
                dmem += 2;
                smem += 2;
            }
            if ((charCount & 1) != 0) {
                dmem[0] = smem[0];
            }
        }

        public void MoveToEndElement(string name) {
            if (_elementName == name && _isEndElement) return;
            var endElementName = String.Concat("/", name);
            var elementCount = endElementName.Length;
            var str = new string(Null, elementCount);
            int count = 0, index = _index;
            fixed (char* p = _xml) {
                if (_length <= index) return;
                do {
                    ++index;
                    ++count;
                    if (count >= elementCount) {
                        fixed (char* cp = str)
                            memcpy(cp, p + index - elementCount, elementCount);
                        if (str == endElementName) {
                            _elementName = name;
                            _isEndElement = true;
                            ++index;
                            break;
                        }
                    }
                } while (true);
                _index = index;
            }
        }
    }
}
